package org.EasyDevelop.Builder;

import org.EasyDevelop.Bean.Constants;
import org.EasyDevelop.Bean.FieldInfo;
import org.EasyDevelop.Bean.TableInfo;
import org.EasyDevelop.Utils.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

public class MapperXMLBuilder {
    private static Logger logger = LoggerFactory.getLogger(MapperXMLBuilder.class);
    private static String base_column_list = "base_column_list";
    private static String base_query_condition = "base_query_condition";
    private static String where_condition_sql = "query_condition";
    private static String base_query_condition_extend = "base_query_condition_extend";
    private static String baseResultMap = "baseResultMap";



    public static void execute(TableInfo tableInfo){
        File folder = new File(Constants.PATH_RESOURCE_MAPPER_XML);

        if(!folder.exists()) folder.mkdirs();

        String mapperName = tableInfo.getBeanName() + StringUtils.upperCaseFirstLetter(Constants.SUFFIX_MAP.get("mappers"));

        File poFile = new File(folder, mapperName + Constants.XML_SUFFIX);

        OutputStream os = null;
        OutputStreamWriter osw = null;
        BufferedWriter bw = null;

        try {
            os = new FileOutputStream(poFile);
            osw = new OutputStreamWriter(os, "UTF8");
            bw = new BufferedWriter(osw);

            /* XML Start */
            bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
                    "\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \n\t\"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">");
            bw.newLine();

            bw.newLine();

            Map<String, String> map = new HashMap<>();
            for (Map.Entry<String, List<FieldInfo>> entry : tableInfo.getKeyIndexMap().entrySet()) {
                for (FieldInfo fieldInfo : entry.getValue()) {
                    map.put(fieldInfo.getFieldName(),fieldInfo.getFieldName());
                }
            }

            //primaryLst
            List<FieldInfo> primaryLst = Optional.ofNullable(tableInfo.getKeyIndexMap().get("PRIMARY")).orElse(Arrays.asList());

            //XML Mapper Start
            bw.write("<mapper namespace=\"" + Constants.PACKAGE_MAPPER + "." + mapperName + "\">");
            bw.newLine();

            bw.newLine();

            /* resultMap */
            generateResultMap(bw,tableInfo,primaryLst);
            bw.newLine();

            bw.newLine();
            /* common return DB Object : base_column_list */
            generateCommonReturnLst(bw,tableInfo);

            bw.newLine();
            /* where sql*/
            generateWhereSQL(bw,tableInfo);


            bw.newLine();
            /* common query Object : base_query_condition */
            commonQuery(bw,tableInfo);

            bw.newLine();
            /* extend query condition : base_query_condition_extend */
            extendQueryCondition(bw,tableInfo);

            /* method */
            bw.newLine();
            bw.write("\t<!--通用方法-->");
            bw.newLine();

            /* selectList */
            generateSelectList(bw,tableInfo);

            /* selectCount */
            generateSelectCount(bw,tableInfo);

            /* insert */
            generateInsert(bw,tableInfo,primaryLst);

            /* insert or update */
            generateInsertOrUpdate(bw,tableInfo,map,primaryLst);

            /* insertBatch */
            generateInsertBatch(bw,tableInfo,primaryLst);

            /* insertOrUpdateBatch*/
            generateInsertOrUpdateBatch(bw,tableInfo,Arrays.asList());

            /* MethodById */
            bw.newLine();
            generatedIdMethods(bw,tableInfo);

            //XML Mapper End
            bw.newLine();
            bw.write("</mapper>");


            bw.flush();

        } catch (Exception e) {
            logger.error("创建Mapper XML失败,{}",e);
        } finally {
            if(bw!=null)
                try{
                    bw.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            if(osw!=null)
                try{
                    osw.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            if(os!=null)
                try{
                    os.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
        }

    }

    private static void generatedIdMethods(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        StringBuilder methodName = new StringBuilder();

        for (Map.Entry<String, List<FieldInfo>> entry : tableInfo.getKeyIndexMap().entrySet()) {
            List<FieldInfo> keyFieldList = entry.getValue();

            //叠加索引相关值
            Integer idx = 0;
            for (FieldInfo fieldInfo : keyFieldList) {
                methodName.append(StringUtils.upperCaseFirstLetter(fieldInfo.getPropertyName()));
                if(idx++<keyFieldList.size()-1){
                    methodName.append("And");
                }
            }

            //select
            CommentBuilder.createXMLComment(bw,"根据" + methodName + "查询","\t");
            bw.write("\t<select id = \"selectBy" + methodName + "\" resultMap = \"" + baseResultMap + "\">");
            bw.newLine();
            bw.write("\t\tSELECT <include refid = \"" + base_column_list + "\"/> FROM " + tableInfo.getTableName() + "\n");
            bw.write("\t\t" +  "WHERE ");
            StringBuilder str = new StringBuilder();
            for (FieldInfo fieldInfo : entry.getValue()) {
                str.append(fieldInfo.getFieldName());
                str.append(" = #{");
                str.append(fieldInfo.getPropertyName());
                str.append("}");
                str.append(" AND ");
            }
            bw.write(str.substring(0,str.lastIndexOf("AND")));
            bw.newLine();
            bw.write("\t</select>");
            bw.newLine();
            bw.newLine();

            //update
            CommentBuilder.createXMLComment(bw,"根据" + methodName + "更新","\t");
            bw.write("\t<update id = \"updateBy" + methodName + "\" parameterType = \"" + Constants.PACKAGE_PO + "." + tableInfo.getBeanName() + "\">");
            bw.newLine();
            bw.write("\t\tUPDATE " + tableInfo.getTableName() + "\n");

            bw.write("\t\t\t<set>");
            bw.newLine();
            int idx2 = tableInfo.getFieldList().size()-1;
            for (FieldInfo fieldInfo : tableInfo.getFieldList()) {


                bw.write("\t\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null \">");
                bw.newLine();

                bw.write("\t\t\t\t\t");
                bw.write(fieldInfo.getFieldName() + " = #{bean." + fieldInfo.getPropertyName() + "}");
                if(idx2-->0){
                    bw.write(", ");
                }
                bw.newLine();

                bw.write("\t\t\t\t</if>");
                bw.newLine();

            }
            bw.write("\t\t\t</set>");
            bw.newLine();


            bw.write("\t\t" +  "WHERE ");
            str = new StringBuilder();
            for (FieldInfo fieldInfo : entry.getValue()) {
                str.append(fieldInfo.getFieldName());
                str.append(" = #{");
                str.append(fieldInfo.getPropertyName());
                str.append("}");
                str.append(" AND ");
            }
            bw.write(str.substring(0,str.lastIndexOf("AND")));
            bw.newLine();
            bw.write("\t</update>");
            bw.newLine();
            bw.newLine();

            //delete
            CommentBuilder.createXMLComment(bw,"根据" + methodName + "删除","\t");
            bw.write("\t<delete id = \"deleteBy" + methodName + "\">");
            bw.newLine();
            bw.write("\t\tDELETE FROM " + tableInfo.getTableName() + "\n");
            bw.write("\t\t" +  "WHERE ");
            str = new StringBuilder();
            for (FieldInfo fieldInfo : entry.getValue()) {
                str.append(fieldInfo.getFieldName());
                str.append(" = #{");
                str.append(fieldInfo.getPropertyName());
                str.append("}");
                str.append(" AND ");
            }
            bw.write(str.substring(0,str.lastIndexOf("AND")));
            bw.newLine();
            bw.write("\t</delete>");
            bw.newLine();
            bw.newLine();


            methodName = new StringBuilder();
        }

    }

    private static void generateInsertOrUpdateBatch(BufferedWriter bw, TableInfo tableInfo, List<FieldInfo> primaryLst) throws IOException {
        bw.newLine();
        bw.write("\t<!-- 批量数据插入或者更新(仅插入有值对象) -->");

        //insert
        bw.newLine();
        bw.write("\t<insert id = \"insertOrUpdateBatch\" parameterType = \"" + Constants.PACKAGE_PO + "." + tableInfo.getBeanName() + "\">");
        bw.newLine();

        //INSERT SQL AND VALUE_KEYS
        StringBuilder str = new StringBuilder();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(!primaryLst.isEmpty() && primaryLst.contains(fieldInfo)) continue;
            str.append(",");
            str.append(fieldInfo.getFieldName());
        }
        bw.write("\t\tINSERT INTO " + tableInfo.getTableName() + "(" + str.substring(1) + ")");

        bw.newLine();

        bw.write("\t\tVALUES");
        bw.newLine();
        //VALUES
        bw.write("\t\t<foreach collection = \"list\" item = \"item\" separator = \",\">");
        bw.newLine();

        str = new StringBuilder();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(primaryLst.contains(fieldInfo)) continue;
            str.append("\t\t\t\t#{item.");
            str.append(fieldInfo.getPropertyName());
            str.append("},\n");
        }

        bw.write("\t\t\t(");
        bw.newLine();

        bw.write(str.substring(0,str.lastIndexOf(",")));

        bw.newLine();
        bw.write("\t\t\t)");

        bw.newLine();

        bw.write("\t\t</foreach>");
        bw.newLine();

        /* DUPLICATE insert->update */
        bw.write("\t\tON DUPLICATE KEY UPDATE");
        bw.newLine();



        str = new StringBuilder();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(!primaryLst.isEmpty() && primaryLst.contains(fieldInfo)) continue;

            str.append("\t\t\t" + fieldInfo.getFieldName() + " = VALUES(" + fieldInfo.getFieldName() + "),\n");

        }



        bw.write(str.substring(0, str.lastIndexOf(",")));
        bw.newLine();

        bw.write("\t</insert>");
        bw.newLine();
    }

    private static void generateInsertBatch(BufferedWriter bw, TableInfo tableInfo, List<FieldInfo> primaryLst) throws IOException {
        bw.newLine();
        bw.write("\t<!-- 批量数据插入(仅插入有值对象) -->");

        bw.newLine();
        bw.write("\t<insert id = \"insertBatch\" parameterType = \"" + Constants.PACKAGE_PO + "." + tableInfo.getBeanName() + "\">");
        bw.newLine();

        StringBuilder str = new StringBuilder();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(primaryLst.contains(fieldInfo)) continue;
            str.append(",");
            str.append(fieldInfo.getFieldName());
        }
        bw.write("\t\tINSERT INTO " + tableInfo.getTableName() + "(" + str.substring(1) + ")");

        bw.newLine();

        bw.write("\t\tVALUES");
        bw.newLine();

        bw.write("\t\t<foreach collection = \"list\" item = \"item\" separator = \",\"  >");
        bw.newLine();

        bw.write("\t\t\t(");
        bw.newLine();

        str = new StringBuilder();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(primaryLst.contains(fieldInfo)) continue;
            str.append("\t\t\t\t#{item.");
            str.append(fieldInfo.getPropertyName());
            str.append("},\n");
        }
        bw.write(str.substring(0,str.lastIndexOf(",")));
        bw.newLine();

        bw.write("\t\t\t)");
        bw.newLine();

        bw.write("\t\t</foreach>");
        bw.newLine();


        bw.write("\t</insert>");
        bw.newLine();
    }

    private static void generateInsertOrUpdate(BufferedWriter bw, TableInfo tableInfo,Map<String,String> map,List<FieldInfo> primaryLst) throws IOException {
        bw.newLine();
        bw.write("\t<!-- 单条数据插入或者更新(仅插入有值对象) -->");
        bw.newLine();
        String res = primaryLst.size()>=0?primaryLst.get(0).getPropertyName():"id";
        bw.write("\t<insert id = \"insertOrUpdate\" parameterType = \"" + Constants.PACKAGE_PO + "." + tableInfo.getBeanName() + "\" useGeneratedKeys = \"true\" keyProperty = \"" + res + "\">");
        bw.newLine();

        bw.write("\t\tINSERT INTO " + tableInfo.getTableName());
        bw.newLine();

        /* trim */
        //foreach
        bw.write("\t\t<trim prefix = \"(\" suffix = \")\" suffixOverrides=\",\" >");
        bw.newLine();

        //SQL
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            bw.write("\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null\" >");
            bw.newLine();

            bw.write("\t\t\t\t" + fieldInfo.getFieldName() + ",");
            bw.newLine();

            bw.write("\t\t\t</if>");
            bw.newLine();
        }
        bw.write("\t\t</trim>");
        bw.newLine();
        //SQL_VAULT
        bw.write("\t\t<trim prefix = \"values (\" suffix = \")\" suffixOverrides=\",\" >");
        bw.newLine();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            bw.write("\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null\" >");
            bw.newLine();

            bw.write("\t\t\t\t#{bean." + fieldInfo.getPropertyName() + "},");
            bw.newLine();

            bw.write("\t\t\t</if>");
            bw.newLine();
        }
        bw.write("\t\t</trim>");
        bw.newLine();

        /* DUPLICATE insert->update */
        bw.write("\t\tON DUPLICATE KEY UPDATE");
        bw.newLine();

        bw.write("\t\t<trim prefix = \"\" suffix = \"\" suffixOverrides=\",\" >");
        bw.newLine();

        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(map.containsKey(fieldInfo.getFieldName()) && null!=map.get(fieldInfo.getFieldName())) continue;
            bw.write("\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null\" >");
            bw.newLine();

            bw.write("\t\t\t\t" + fieldInfo.getFieldName() + " = VALUES(" + fieldInfo.getPropertyName() + "),");
            bw.newLine();

            bw.write("\t\t\t</if>");
            bw.newLine();
        }
        bw.write("\t\t</trim>");
        bw.newLine();

        bw.write("\t</insert>");
        bw.newLine();
    }

    private static void generateInsert(BufferedWriter bw, TableInfo tableInfo,List<FieldInfo> primaryLst) throws IOException {
        bw.newLine();
        bw.write("\t<!-- 单条数据插入(仅插入有值对象) -->");
        bw.newLine();
        String res = primaryLst.size()>=0?primaryLst.get(0).getPropertyName():"id";
        bw.write("\t<insert id = \"insert\" parameterType = \"" + Constants.PACKAGE_PO + "." + tableInfo.getBeanName() + "\" useGeneratedKeys = \"true\" keyProperty = \"" + res + "\">");
        bw.newLine();
        //判断是否是自增id
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            if(fieldInfo.getIsAutoIncrement()!=null && fieldInfo.getIsAutoIncrement()){
                bw.write("\t\t<selectKey keyProperty = \"bean." + fieldInfo.getPropertyName() + "\" resultType = \"" + fieldInfo.getJavaType() + "\" order = \"AFTER\">");
                bw.newLine();
                bw.write("\t\t\tSELECT LAST_INSERT_ID()");
                bw.newLine();
                bw.write("\t\t</selectKey>");
                bw.newLine();
                break;
            }
        }
        bw.write("\t\tINSERT INTO " + tableInfo.getTableName());
        bw.newLine();

        //foreach
        bw.write("\t\t<trim prefix = \"(\" suffix = \")\" suffixOverrides=\",\" >");
        bw.newLine();
        //SQL
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            bw.write("\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null\" >");
            bw.newLine();

            bw.write("\t\t\t\t" + fieldInfo.getFieldName() + ",");
            bw.newLine();

            bw.write("\t\t\t</if>");
            bw.newLine();
        }
        bw.write("\t\t</trim>");
        bw.newLine();

        //SQL_VAULT
        bw.write("\t\t<trim prefix = \"values (\" suffix = \")\" suffixOverrides=\",\" >");
        bw.newLine();
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            bw.write("\t\t\t<if test = \"bean." + fieldInfo.getPropertyName() + " != null\" >");
            bw.newLine();

            bw.write("\t\t\t\t#{bean." + fieldInfo.getPropertyName() + "},");
            bw.newLine();

            bw.write("\t\t\t</if>");
            bw.newLine();
        }
        bw.write("\t\t</trim>");
        bw.newLine();

        bw.write("\t</insert>");
        bw.newLine();
    }

    private static void generateSelectCount(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<select id = \"" + "selectCount" + "\" resultType = \"java.lang.Long\">");
        bw.newLine();

        bw.write("\t\tSELECT COUNT(1) FROM " + tableInfo.getTableName() + "\n\t\t<include refid=\"" + where_condition_sql + "\" />");
        bw.newLine();

        bw.write("\t</select>");
        bw.newLine();
    }

    private static void generateSelectList(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<select id = \"" + "selectList" + "\" resultMap = \"" + baseResultMap + "\">");
        bw.newLine();

        //SQL
        bw.write("\t\tSELECT <include refid=\"" + base_column_list + "\"/> FROM " + tableInfo.getTableName() +" <include refid=\"" + where_condition_sql + "\"/>");

        //SORT
        bw.newLine();
        bw.write("\t\t<if test = \"query.orderBy != null \">");
        bw.newLine();

        bw.write("\t\t\tORDER BY ${query.orderBy}");
        bw.newLine();

        bw.write("\t\t</if>");
        bw.newLine();
        //LIMIT
        bw.write("\t\t<if test = \"query.simplePage != null \">");
        bw.newLine();

        bw.write("\t\t\tLIMIT #{query.simplePage.start} , #{query.simplePage.end}");
        bw.newLine();

        bw.write("\t\t</if>");
        bw.newLine();

        bw.newLine();

        bw.write("\t</select>");
        bw.newLine();
    }

    private static void generateCommonReturnLst(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<!--通用查询结果集-->");
        bw.newLine();
        bw.write("\t<sql id = \"" + base_column_list + "\">");
        bw.newLine();
        bw.write("\t\t" + String.join(",",tableInfo.getFieldList().stream().map(FieldInfo::getFieldName).collect(Collectors.toList())));
        bw.newLine();
        bw.write("\t</sql>");
        bw.newLine();
    }

    private static void generateWhereSQL(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<sql id = \"" + where_condition_sql + "\">");
        bw.newLine();
        bw.write("\t\t<where>");
        bw.newLine();

        bw.write("\t\t\t<include refid = \"" + base_query_condition + "\"/>");
        bw.newLine();

        bw.write("\t\t\t<include refid = \"" + base_query_condition_extend + "\"/>");
        bw.newLine();

        bw.write("\t\t</where>");
        bw.newLine();
        bw.write("\t</sql>");
        bw.newLine();
    }

    private static void generateResultMap(BufferedWriter bw, TableInfo tableInfo,List<FieldInfo> primaryLst) throws IOException {
        //resultMap comment
        bw.write("<!--实体映射-->");
        bw.newLine();

        bw.newLine();
        //resultMap start
        String poClazz = Constants.PACKAGE_PO + "." + tableInfo.getBeanName();
        bw.write("\t<resultMap id=\"baseResultMap\" type=\"" + poClazz + "\">");
        bw.newLine();

        //resultMap element
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {
            //field comment
            bw.write("\t\t<!--" + fieldInfo.getComment() + "-->");
            bw.newLine();

            //field reflection
            if(primaryLst.contains(fieldInfo)){
                bw.write("\t\t<id property=\"" + fieldInfo.getPropertyName() + "\" column=\"" + fieldInfo.getFieldName() + "\" /> ");
            }
            else {
                bw.write("\t\t<result property=\"" + fieldInfo.getPropertyName() + "\" column=\"" + fieldInfo.getFieldName() + "\" /> ");
            }
            bw.newLine();
        }

        //resultMap end
        bw.write("\t</resultMap>");
    }

    private static void extendQueryCondition(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<!--扩展查询条件-->");
        bw.newLine();
        bw.write("\t<sql id = \"" + base_query_condition_extend + "\">");
        bw.newLine();

        //foreach fieldInfos
        for (FieldInfo fieldInfo : tableInfo.getExtendFieldLst()) {

//            System.out.println("extendField = " + fieldInfo);

            //special handle
            String specialHandeStr = "";

            if(ArrayUtils.contains(Constants.SQL_STRING_TYPES,fieldInfo.getSqlType())){
                specialHandeStr = "and " + fieldInfo.getFieldName() + " like concat('%',#{query." + fieldInfo.getPropertyName() + "},'%')";
            }else if (ArrayUtils.contains(Constants.SQL_DATE_TYPES,fieldInfo.getSqlType()) || ArrayUtils.contains(Constants.SQL_DATE_TIME_TYPES,fieldInfo.getSqlType())){
                String symbol = " >= ";
                String followUp = "";
                if(fieldInfo.getPropertyName().endsWith("Start")){
                    symbol = " >= ";
                    followUp = "";
                }
                if(fieldInfo.getPropertyName().endsWith("End")){
                    symbol = " < date_sub(";
                    followUp = ", interval -1 day)";
                }

                specialHandeStr = "<![CDATA[ and " + fieldInfo.getFieldName() + symbol + "str_to_date(#{query." + fieldInfo.getPropertyName() + "}, '%Y-%m-%d')" + followUp + " ]]>";
            }

            // common condition
            bw.write("\t\t<if test = \"query." + fieldInfo.getPropertyName() + " != null" + " and query." + fieldInfo.getPropertyName() + " != ''" + "\">");
            bw.newLine();

            bw.write("\t\t\t" + specialHandeStr);
            bw.newLine();

            bw.write("\t\t</if>");
            bw.newLine();
            bw.newLine();

        }

        // extend query end
        bw.write("\t</sql>");
        bw.newLine();
    }

    private static void commonQuery(BufferedWriter bw, TableInfo tableInfo) throws IOException {
        bw.write("\t<!--通用查询条件-->");
        bw.newLine();

        bw.write("\t<sql id = \"" + base_query_condition + "\">");
        bw.newLine();

        //foreach fieldInfos
        for (FieldInfo fieldInfo : tableInfo.getFieldList()) {

            //special handle
            String queryCondition = "";
            if(ArrayUtils.contains(Constants.SQL_STRING_TYPES,fieldInfo.getSqlType()))
                queryCondition = " and query." + fieldInfo.getPropertyName() + " != ''";



            // common condition
            bw.write("\t\t<if test = \"query." + fieldInfo.getPropertyName() + " != null" + queryCondition + "\">");
            bw.newLine();
            bw.write("\t\t\tand " + fieldInfo.getPropertyName() + " = #{query." + fieldInfo.getPropertyName() + "}");
            bw.newLine();
            bw.write("\t\t</if>");
            bw.newLine();
            bw.newLine();

        }
        bw.newLine();

        // common query end
        bw.write("\t</sql>");
        bw.newLine();
    }
}
